之前介绍符号化文件的时候,使用atos命令可以将符号堆栈信息变的可读。方法如下:
atos -o dYSM -arch **arch -l imageAddress stackAddress
在真实的环境中使用的时候需要准备的dYSM
文件远远不止archive出来的app二进制文件。奔溃的堆栈中都会出现系统的库,例如:
CoreFundation
UIkit
libdyld.dylib
...
这些库的符号文件没有在archive的时候一并集成到app.dSYM文件中。在使用atos命令方式解析的时候,你需要找到对应二进制镜像的符号文件才能解析。以当前测试的TestCrash
文件为例,需要在Xcode中找到对应的系统符号文件
~/Library/Developer/Xcode/iOS DeviceSupport/
// 当前我测试的系统版本是11.4.1,系统的符号文件在对应版本的文件夹下
~/Library/Developer/Xcode/iOS DeviceSupport/11.4.1 (15G77)/Symbols
常见的Foundation等系统库符号文件在这个文件中
常见的lib符号文件在这个文件中
更多的符号文件可以在这个地址中找到。
符号化文件(swift server后台处理)
符号文件已经准备齐全,之后就是解析的过程了,在之前的文章中,上报的符号文件已经拆分成了diction的方式,每个diction单元中的信息包含:
- imageName
- imageLoadAddress
- stackAddress
解析的时候,先拿imageName去系统库中找有没有匹配的二进制符号文件。这里写了一个简单的代码:
// 这里配置一张表,当前系统可以直接索引路径
func systemSymbolPath(bySystemVersion: String, imageName: String) -> String? {
let prePath: String = "/Users/handongwang/Library/Developer/Xcode/iOS DeviceSupport"
let libSuffixPath: String = "Symbols/usr/lib"
let frameworkSuffixPath: String = "Symbols/System/Library/Frameworks"
var suffixPath = frameworkSuffixPath
if imageName.hasPrefix("lib") {
suffixPath = libSuffixPath
}
if bySystemVersion == "11.4.1" {
return prePath + "/11.4.1 (15G77)/" + suffixPath
}
return nil
}
查找的方式通过find command进行,下面是swift是用shell commad的一个简短的封装。(ps:使用perfect自己的SysProcess一直没有成功,使用了如下的方法):
func shell(launchPath: String, arguments: [String]) -> String?
{
let task = Process()
task.launchPath = launchPath
task.arguments = arguments
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: String.Encoding.utf8)
if let value = output, value.count > 0 {
//remove newline character.
let lastIndex = value.index(before: value.endIndex)
return String(value[value.startIndex ..< lastIndex])
}
return output
}
在实际收到crash上传文件的出路handler如下:
public static func requestHandler(request: HTTPRequest, response: HTTPResponse) {
print(request.postFileUploads ?? "nothing")
func errorHandler(response: HTTPResponse) {
response.sendError(message: "数据解析错误")
}
if let uploads = request.postFileUploads, uploads.count > 0 {
// 当前测试中智慧传递一个文件
if let upload = uploads.first {
if let data = NSData.init(contentsOfFile: upload.tmpFileName) {
if let dict = try? JSONSerialization.jsonObject(with: data as Data, options: .mutableContainers), let dictObject = dict as? NSDictionary {
// 数据解析成功,使用命令行解析数据
if let stackInfoArray = dictObject["stackInfo"] as? NSArray,
stackInfoArray.count > 0,
let arch = dictObject["arch"] as? String,
let systemVersion = dictObject["systemVersion"] as? String {
let mutableArray = NSMutableArray()
for info in stackInfoArray {
if let infoDict = info as? [String : String], let nameString = infoDict["name"],
let imageAddress = infoDict["imageAddress"], let strStackAddress = infoDict["strStackAddress"]{
if let systemSymbolPath = systemSymbolPath(bySystemVersion: systemVersion),
systemSymbolPath.count > 0 {
// 系统文件中找符号文件
if let findPath = shell(launchPath: "/usr/bin/find", arguments: ["\(systemSymbolPath)" , "-iname" , "\(nameString)"]), findPath.count > 0 {
// 这里需要去除换行符
let pathWhoutNewLine = findPath.stringByReplacing(string: "\n", withString: "")
if let output = shell(launchPath: "/usr/bin/atos", arguments: ["-arch" , "\(arch)" , "-o" , "\(pathWhoutNewLine)" , "-l" , "\(imageAddress)" , "\(strStackAddress)"]) {
mutableArray.add(["name": nameString, "symbolString": output])
}
}
else {
if let output = shell(launchPath: "/usr/bin/atos", arguments: ["-arch" , "\(arch)" , "-o" , "/Users/handongwang/Desktop/TestCrashFile/TestCrash.app.dSYM/Contents/Resources/DWARF/TestCrash" , "-l" , "\(imageAddress)" , "\(strStackAddress)"]) {
mutableArray.add(["name": nameString, "symbolString": output])
}
}
}
}
}
print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
print(mutableArray)
}
}
else {
errorHandler(response: response)
}
}
else {
errorHandler(response: response)
}
}
else {
errorHandler(response: response)
}
}
else {
errorHandler(response: response)
}
}
解析的过程变成了找到对应的符号文件,使用atos命令逐个diction开始解析
// 系统libdyld.dylib文件符号化过程
atos -arch arm64 -o ~/Library/Developer/Xcode/iOS\ DeviceSupport/11.4.1\ \(15G77\)/Symbols/usr/lib/system/libdyld.dylib -l 0x1829E1000 0x00000001829e1fc0
// 结果展示
start (in libdyld.dylib) + 4
// 系统UIKit文件符号化过程
atos -arch arm64 -o ~/Library/Developer/Xcode/iOS\ DeviceSupport/11.4.1\ \(15G77\)/Symbols/System/Library/Frameworks/UIKit.framework/UIKit -l 0x18CC53000 0x000000018ce71890
// 结果展示
-[UIWindow sendEvent:] (in UIKit) + 3160
最终手动符号文件完成打印的结果为:
(
{
name = CoreFoundation;
symbolString = "__exceptionPreprocess (in CoreFoundation) + 252";
},
{
name = "libobjc.A.dylib";
symbolString = "objc_exception_throw (in libobjc.A.dylib) + 56";
},
{
name = CoreFoundation;
symbolString = "_CFArgv (in CoreFoundation) + 0";
},
{
name = CoreFoundation;
symbolString = "-[__NSArrayM insertObject:atIndex:] (in CoreFoundation) + 1412";
},
{
name = TestCrash;
symbolString = "-[ViewController function9] (in TestCrash) (ViewController.m:89)";
},
{
name = UIKit;
symbolString = "-[UIApplication sendAction:to:from:forEvent:] (in UIKit) + 96";
},
{
name = UIKit;
symbolString = "-[UIControl sendAction:to:forEvent:] (in UIKit) + 80";
},
{
name = UIKit;
symbolString = "-[UIControl _sendActionsForEvents:withEvent:] (in UIKit) + 440";
},
{
name = UIKit;
symbolString = "-[UIControl touchesEnded:withEvent:] (in UIKit) + 572";
},
{
name = UIKit;
symbolString = "-[UIWindow _sendTouchesForEvent:] (in UIKit) + 2428";
},
{
name = UIKit;
symbolString = "-[UIWindow sendEvent:] (in UIKit) + 3160";
},
{
name = UIKit;
symbolString = "-[UIApplication sendEvent:] (in UIKit) + 340";
},
{
name = UIKit;
symbolString = "__dispatchPreprocessedEventFromEventQueue (in UIKit) + 2340";
},
{
name = UIKit;
symbolString = "__handleEventQueueInternal (in UIKit) + 4744";
},
{
name = UIKit;
symbolString = "__handleHIDEventFetcherDrain (in UIKit) + 152";
},
{
name = CoreFoundation;
symbolString = "__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ (in CoreFoundation) + 24";
},
{
name = CoreFoundation;
symbolString = "__CFRunLoopDoSources0 (in CoreFoundation) + 276";
},
{
name = CoreFoundation;
symbolString = "__CFRunLoopRun (in CoreFoundation) + 1204";
},
{
name = CoreFoundation;
symbolString = "CFRunLoopRunSpecific (in CoreFoundation) + 552";
},
{
name = GraphicsServices;
symbolString = "getProgressBinaryImagesInfo (in TestCrash) (LHCrashTool.m:244)";
},
{
name = UIKit;
symbolString = "UIApplicationMain (in UIKit) + 236";
},
{
name = TestCrash;
symbolString = "main (in TestCrash) (main.m:14)";
},
{
name = "libdyld.dylib";
symbolString = "start (in libdyld.dylib) + 4";
}
)
在看一眼xcode中自动符号的文件:
和上面手动符号化的文件一样。到此符号化文件已经完成。当前还存在一些问题:
- 符号文件的时间过程,需要调研,单个文件的符号时间在3s左右,时间耗时过长,需要优化
存储符号化文件
存储符号化的过程,当前使用的是swift server + mongodb的方式。